﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Warp9
{
    class Terrain
    {
        public VertexPositionColor[] Vertices;
        public VertexPositionColor[] VerticesToRender; //This should become a vertex buffer.
        public List<int> SurfaceIndex;
        private float lowestPoint, highestPoint;
        private Random rand;
        public Terrain(int count, float scale)
        {
            rand = new Random();
            Vertices = GenerateTerrain(count, new Vector3(scale));
            VerticesToRender = ToCircle();
        }
        private VertexPositionColor[] GenerateTerrain(int xCount, Vector3 scale)
        {
            VertexPositionColor[] newVerts = new VertexPositionColor[(xCount * 2) + 2];
            SurfaceIndex = new List<int>(xCount * 2 + 2) { };
            Vector3 origin = new Vector3(0, 0, 0);

            for (int i = 0; i < newVerts.Length - 1; i++)
            {
                newVerts[i] = new VertexPositionColor((new Vector3(i, 0f, 0) * scale) + origin, Color.Brown);
                i++;
                newVerts[i] = new VertexPositionColor((new Vector3(i - 1, 25f, 0) * scale) + origin, new Color(25, 25, 25));
                SurfaceIndex.Add(i);
            }
            Randomize(newVerts, SurfaceIndex);
            //Erode(newVerts,SurfaceIndex);
            return newVerts;
        }

        private void Randomize(VertexPositionColor[] verts, List<int> index)
        {
            float offset;
            foreach (int x in index)
            {
                offset = rand.Next(0, 100) * 0.01f;
                verts[x].Position.Y += offset;
            }
            verts[index[index.Count - 1]].Position.Y = verts[index[0]].Position.Y;
        }

        private VertexPositionColor[] ToCircle()  //Change this to generate a vertex buffer.
        {
            VertexPositionColor[] circleVerts = new VertexPositionColor[Vertices.Length];
            float distanceBetween = MathHelper.TwoPi / (this.Vertices.Length - 2);
            float X;
            float Y;
            float distance;
            for (int x = 0; x < this.Vertices.Length; x++)
            {
                distance = 10 + (this.Vertices[x].Position.Y * 2);
                Y = distance * (float)Math.Cos(x * distanceBetween);
                X = distance * (float)Math.Sin(x * distanceBetween);

                circleVerts[x].Position = new Vector3(X, Y, 0);
                circleVerts[x].Color = Vertices[x].Color;

            }
            GetRelativeHeight();
            //Set color by height.
            for (int x = 0; x < this.SurfaceIndex.Count; x++)
            {
                float height = Vertices[SurfaceIndex[x]].Position.Y;
                float percentHeight = height / (highestPoint - lowestPoint);
                {
                    //circleVerts[SurfaceIndex[x]].Color = new Color(percentHeight, percentHeight, percentHeight);
                }
            }
            //return Vertices;  //Uncomment for a quick way to check the "internal" view of the grid.
            return circleVerts;
        }

        private void GetRelativeHeight()
        {
            highestPoint = 0f;
            lowestPoint = 1000f;
            for (int x = 0; x < SurfaceIndex.Count; x++)
            {
                float currentPoint = Vertices[SurfaceIndex[x]].Position.Y;
                if (currentPoint < lowestPoint)
                    lowestPoint = currentPoint;
                if (currentPoint > highestPoint)
                    highestPoint = currentPoint;
            }
        }

        /// <summary>
        /// Creates mountains.
        /// </summary>
        /// <param name="maximumRepetitions"></param>
        public void SpawnMountains(int maximumRepetitions)
        {
            for (int run = 0; run < maximumRepetitions; run++)
            {
                int randomStart = rand.Next(0, SurfaceIndex.Count); //Random location
                int range = SurfaceIndex.Count / rand.Next(SurfaceIndex.Count / 40, SurfaceIndex.Count / 2); //Width of mountain.
                float gradientIncrease = (float)rand.Next(1, 101) * 0.0001f; //Random "steepness".
                float gradient = 0f;
                for (int x = -range; x < range; x++)
                {
                    if (x <= 0)
                    {
                        gradient += gradientIncrease;
                        gradientIncrease += 0.01f;
                    }
                    else
                    {
                        gradient -= gradientIncrease;
                        gradientIncrease -= 0.01f;
                    }

                    Vertices[SurfaceIndex[RollCurrentIndex(randomStart + x)]].Position.Y += gradient;
                }
            }
            Erode(1);
        }
        /// <summary>
        /// Erode the surface of the terrain, creates large flat areas and smooths mountains.
        /// </summary>
        /// <param name="maximumRepetitions"></param>
        public void Erode(int maximumRepetitions)
        {
            VertexPositionColor[] verts = Vertices;
            List<int> index = SurfaceIndex;
            int randomStart;
            int x = 0, nextX = 0, lastX = 0;
            int numLocations = index.Count / (index.Count / 2);
            float initialHeight = 0f;
            for (int z = 0; z < rand.Next(numLocations / 2, numLocations); z++) //Picks $numLocations/2 through $numLocations to flatten over.
            {
                randomStart = rand.Next(0, index.Count); //Pick a random position on the grid.
                int range = index.Count / rand.Next(index.Count / 40, index.Count / 10); //Random range, never more than 10% of the surface.
                initialHeight = verts[index[RollCurrentIndex(z + randomStart)]].Position.Y;
                for (int y = -range; y < range; y++) //Flattens a radius of $range units.
                {
                    for (int loop = 0; loop < 40; loop++)
                    {
                        x = index[RollCurrentIndex(z + randomStart + y)];
                        nextX = index[NextVertIndex(x)];
                        lastX = index[LastVertIndex(x)];
                        verts[x].Position.Y = MathHelper.SmoothStep(verts[index[RollCurrentIndex(x)]].Position.Y, initialHeight, 0.56f);
                        //verts[x].Position.Y = MathHelper.SmoothStep(verts[index[RollCurrentIndex(x)]].Position.Y, (rand.Next(0, 2) == 0) ? verts[lastX].Position.Y : verts[nextX].Position.Y, 0.99f); //Very slow.
                    }
                }
            }

            for (int reps = 0; reps < maximumRepetitions; reps++)
            {
                randomStart = rand.Next(0, index.Count);


                foreach (int y in index)
                {
                    x = index[RollCurrentIndex(y)];
                    nextX = index[NextVertIndex(y)];
                    lastX = index[LastVertIndex(y)];

                    verts[x].Position.Y = MathHelper.SmoothStep(verts[x].Position.Y, (rand.Next(0, 2) == 0) ? verts[lastX].Position.Y : verts[nextX].Position.Y, 0.25f);

                }
            }
            verts[index[index.Count - 1]].Position.Y = verts[index[0]].Position.Y;
            VerticesToRender = ToCircle();
        }
        private int NextVertIndex(int index)
        {
            return mod(index + 1, SurfaceIndex.Count);
        }
        private int LastVertIndex(int index)
        {
            return mod(index - 1, SurfaceIndex.Count);
        }
        private int RollCurrentIndex(int index)
        {
            return mod(index, SurfaceIndex.Count);
        }

        private int mod(int a, int b)
        {
            return (a % b + b) % b;
        }
    }
}
